#include "DataBase.h"

#include "../util/definitions.h"
#include "../shared/Request.h"

#include <iostream>
#include <unistd.h>
#include <fcntl.h>

DataBase::DataBase() :
	db_mutex(DATABASE_SEMAPHORE_F, DATABASE_SEMAPHORE_L, 1),
	rc_mutex(DATABASE_SEMAPHORE_F, DATABASE_SEMAPHORE_R, 1) {

	/* se inicializa el mutex en 1 para que un proceso pueda entrar inicialmente*/

	/* se inicializa la variable readers count en 0 */
	readersCount.create(DATABASE_SEMAPHORE_F,DATABASE_SEMAPHORE_R);
	readersCount.write(0);

	db.clear();
}

DataBase::~DataBase() {
	while ( ! db.empty() ) {
		db.front().free();
		db.pop_front();
	}
	readersCount.free();
}

void DataBase::load() {

	/* cargo la base de datos desde el archivo */
	int fd = open(DATABASE_FILE, O_CREAT | O_RDWR , 0660);

	char letra 	= 'a';
	char count 	=  0;
	bool is_eof = false;

	RegisterBlock buffer;

	while ( ! is_eof ) {

		SharedMemory<RegisterBlock> db_block;	/* se crea la memoria compartida */

		db_block.create(DATABASE_SHMEM_F, letra + count);

		is_eof = (buffer.load(fd) == -1);	/* leo los bloques de registro desde archivo */

		db_block.write(&buffer);		/* copio los registros en la memoria compartida */

		db.push_back(db_block);			/* agrego el bloque de registros a la base de datos */

		++count;
	}

	close(fd);

}

void DataBase::save() {

	int fd = open(DATABASE_FILE, O_CREAT | O_RDWR , 0660); 		/* guardo la base de datos a disco */

	std::list< SharedMemory<RegisterBlock> >::iterator it = db.begin();

	for (; it != db.end(); ++it) {

		RegisterBlock *regs = (*it).get();	/* obtengo el bloque a guardar */

		regs->save(fd);						/* escribo el bloque a archivo */
	}

	close(fd);
}

void DataBase::finalize() {

	/* se liberan la memoria compartida y los semaforos */

	while ( ! db.empty() ) {
		db.front().free();
		db.pop_front();
	}

	db_mutex.remove();
	rc_mutex.remove();

	readersCount.free();
}


bool DataBase::hasRegister(std::string name) {

	const DBRegister *reg = NULL;

	std::list<SharedMemory<RegisterBlock> >::iterator it = db.begin();

	/* busco en todos los bloques hasta obtener el registro */

	for (; it != db.end() && reg == NULL; ++it) {

		RegisterBlock *regs = (*it).get();
		reg = regs->get(name);		/* obtengo el registro */

		if (reg)
			return true;			/* el registro existe */
	}

	return false;
}


const DBRegister* DataBase::getRegister(std::string name) {

	readLock();

	const DBRegister *reg = NULL;
	std::list<SharedMemory<RegisterBlock> >::iterator it = db.begin();

	/* busco en todos los bloques hasta obtener el registro */

	for (; it != db.end() && reg == NULL; ++it) {

		RegisterBlock *regs = (*it).get();
		reg = regs->get(name);	/* obtengo el registro */

		if (reg)
			reg->print();		/* muestro el registro obtenido */
	}

	readUnlock();

	return reg;
}


int DataBase::addRegister(const DBRegister *reg) {

	int result = result_error;

	if ( reg != NULL ) {

		writeLock();	/* lockeo la base de datos para escritura */

		if ( hasRegister(reg->name) ) {

			result = result_exists;	/* checkeo que no exista el registro */
		}
		else {

			/* no existe -> busco en todos los bloques un lugar para insertar el registro */

			int id = -1;
			std::list<SharedMemory<RegisterBlock> >::iterator it = db.begin();

			for (; it != db.end() && id == -1; ++it) {

				RegisterBlock *regs = (*it).get();	/* obtengo los registros de la base de datos */

				id = regs->add(reg);	/* agrego el registro */

				regs->print(id);
			}

			if (id == -1) {

				char letra 	= 'a';	/* no hay mas lugar .. creo un nuevo bloque */

				SharedMemory<RegisterBlock> db_block;

				db_block.create(DATABASE_SHMEM_F, letra + db.size());

				db_block.get()->init();				/* inicializo el bloque */

				int id = db_block.get()->add(reg);	/* agrego el registro */

				db_block.get()->print(id);

				db.push_back(db_block);		/* agrego el bloque de registros a la base de datos */
			}

			result = result_ok;
		}

		writeUnlock();	/* desbloqueo la base de datos */
	}

	return result;
}

int DataBase::updateRegister(const DBRegister *reg) {

	int result = result_error;

	if ( reg != NULL ) {

		writeLock();	/* lockeo la base de datos para escritura */

		std::list<SharedMemory<RegisterBlock> >::iterator it = db.begin();

		int id = -1;

		/* busco en todos los bloques el registro a modificar */
		for (; it != db.end() && id == -1; ++it) {

			RegisterBlock *regs = (*it).get();	/* obtengo los registros de la base de datos */

			id = regs->update(reg);	/* modifico el registro */

			regs->print(id);
		}

		if (id != -1)
			result = result_ok;	/* indico resultado exitoso */

		writeUnlock();	/* desbloqueo la base de datos */
	}

	return result;
}

int DataBase::removeRegister(const DBRegister *reg) {

	int result = result_error;

	if ( reg != NULL ) {

		writeLock();	/* lockeo la base de datos para escritura */

		std::list<SharedMemory<RegisterBlock> >::iterator it = db.begin();

		int id = -1;

		/* busco en todos los bloques el registro a eliminar */
		for (; it != db.end() && id == -1; ++it) {

			RegisterBlock *regs = (*it).get();	/* obtengo los registros de la base de datos */

			id = regs->remove(reg);	/* elimino el registro */

			reg->print();
		}

		if ( id != -1 )
			result = result_ok;	/* indico resultado exitoso */

		writeUnlock();	/* desbloqueo la base de datos */
	}

	return result;
}

void DataBase::readLock() {

	rc_mutex.wait();

	int new_readersCount = * readersCount.get();
	++ new_readersCount;

	readersCount.write(new_readersCount);

	if (new_readersCount == 1)
		db_mutex.wait();	/* si hay alguien escribiendo espero que termine */

	rc_mutex.signal();
}

void DataBase::readUnlock() {

	rc_mutex.wait();

	int new_readersCount = * readersCount.get();
	-- new_readersCount;

	readersCount.write(new_readersCount);

	if ( new_readersCount == 0 )
		db_mutex.signal();	/* no hay nadie leyendo por lo que se puede escribir */

	rc_mutex.signal();
}

void DataBase::writeLock() {
	db_mutex.wait();
}

void DataBase::writeUnlock() {
	db_mutex.signal();
}
